package server;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

public class Bootstrap {

    /**
     * 定义socket监听的端⼝号
     */
    private int port = 8080;

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    /**
     * Minicat启动需要初始化展开的⼀些操作
     */
    public void start() throws Exception {
        // 加载解析相关的配置，web.xml  这个是第4个的方法
//        loadServlet();



        // 定义一个线程池，这里是线程池的参数
        int corePoolSize = 10;//初始大小
        int maximumPoolSize = 50;//线程池最大数量
        long keepAliveTime = 100L;//线程活跃时间，就是超过这个时间，就把空余线程给销毁掉
        TimeUnit unit = TimeUnit.SECONDS;//活跃时间的单位，这里设置的是秒
        //这个是请求的队列，这里的50是给的一个长度，ArrayBlockingQueue这是下面的是这个BlockingQueue的实现类
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(50);
        //线程工程
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        //拒绝策略
        RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();

        //定义一个线程池 给6用的，这里是定义的线程池，上面这堆是自己的参数
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                unit,
                workQueue,
                threadFactory,
                handler);


        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("=====>>>Minicat start on port：" + port);


        //1.http://127.0.0.1:8080/
        // 启动方式，直接启动main函数，这个时候还没有任何配置文件，与其他的类，就这一个类跟方法
        // 页面访问能进到类里面来了，能打印日志,但是浏览器页面显示异常，因为没有返回请求头给浏览器，
        // 页面会显示无法访问，但是方法以及打印日志了，进来了
        // 下一步改造，给浏览器返回请求头，与请求体
        /*
        while (true) {
            Socket socket = serverSocket.accept();
            // 有了socket，接收到请求，获取输出流
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write("Hello Minicat!".getBytes());
            System.out.println("Hello Minicat!");
            socket.close();
        }*/


        // 2.完成Minicat 1.0版本
        // 需求：浏览器请求http://localhost:8080,返回⼀个固定的字符串到⻚⾯"Hello Minicat!"
        // 此时给新增了放回页面请求头的一个类 HttpProtocolUtil，封装了，页面的请求头字符串信息 200与404什么的
        /* return "HTTP/1.1 200 OK \n" +
                "Content-Type: text/html \n" +
                "Content-Length: " + contentLength + " \n" +  //contentLength有些浏览器，你文本内容长度不给他带，会不给你显示
                "\r\n";*/  //这里后面还没拼接请求体，所以换个行给后面的内容，这里只是请求头
        /*while (true) {
            Socket socket = serverSocket.accept();
            // 有了socket，接收到请求，获取输出流
            OutputStream outputStream = socket.getOutputStream();
            String data = "Hello Minicat!";
            String responseText = HttpProtocolUtil.getHttpHeader200(data.getBytes().length) + data;
            outputStream.write(responseText.getBytes());
            socket.close();
        }*/


        //3. 获取浏览器过来的请求头信息
        //main方法执行以下，就获取到浏览器过来的请求头信息内容了
        /*while (true) {
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();

            // 从流中获取请求信息
            int count=0;
            while(count==0){
                count=inputStream.available();
            }
            byte[] bytes=new byte[count];
            inputStream.read(bytes);
            System.out.println("获取到的浏览器过来的请求头信息:"+new String(bytes));
            socket.close();
        }*/


        // 4.完成Minicat 2.0版本
        // 需求：封装Request和Response对象，返回html静态资源文件
        // 新增了Request, Response, StaticResourceUtil, index.html
        // StaticResourceUtil这个是处理系统资源文件的，就是页面文件，实现读进来，然后再吐给浏览器
        // 请求地址:  http://127.0.0.1:8080/index.html  方法都会被调用，页面也得到了渲染
        // 执行方式，还是启动当前的main方法去启动
        /*while (true) {
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream(); //这里跟上面一样，拿到浏览器的请求流数据

            // 封装Request对象和Response对象
            Request request = new Request(inputStream);
            Response response = new Response(socket.getOutputStream());

            //  这里实现，讲页面请求路径，发送给下面执行，然后读到页面的请求路径对应的静态资源文件，即页面
            // 然后把页面内容以输入流读入，再转换为输出流给到socket.getOutputStream()里面，吐回给页面
            // 这样页面就得到了请求对应的文件页面的渲染效果了
            response.outputHtml(request.getUrl());
            socket.close();//然后关闭这个socket
            // 当下一次请求的时候，这个socket又重新被激活了，因为这里是while true的循环
        }*/


        //4. 完成Minicat 3.0版本, 可以请求动态资源调用了
        // 准备整servlet了, 实现自己写的LagouServlet能被浏览器调用
        // 新增了接口与类 Servlet,HttpServlet,HttpServlet, web.xml,
        // 本类中新增方法，loadServlet() 解析加载web.xml文件用的，放在当前方法的最上面了，调用的入口
        // 本类中加一个servletMap的map，用于存放从web.xml中解析出来的对应的servlet地址
        // pom.xml加入jar包，解析web.xml用的 dom4j与jaxen（xpath标签解析的）
        // 启动方式还是，用main来启动，
        // 访问方式 http://127.0.0.1:8080/index.html  静态文件访问
        // 访问方式 http://127.0.0.1:8080/lagou       动态请求访问
        /*while (true) {
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();

            // 封装Request对象和Response对象
            Request request = new Request(inputStream);
            Response response = new Response(socket.getOutputStream());

            // 静态资源处理
            if (servletMap.get(request.getUrl()) == null) {
                response.outputHtml(request.getUrl());
            } else {
                // 动态资源servlet请求
                HttpServlet httpServlet = servletMap.get(request.getUrl());
                httpServlet.service(request, response);
            }

            socket.close();

        }*/


        //5. 多线程改造
        // 实现一个servlet处理超长时间，导致另一个servlet请求不响应的问题
        // 新增RequestProcessor类
        // 访问： http://127.0.0.1:8080/lagou   这里servlet里面加睡眠，让他一直占着
        // 访问： http://127.0.0.1:8080/index.html  这里继续访问
        // 可以发现，两个都可以访问，不受影响了
        /*while(true){
            Socket socket = serverSocket.accept();
            RequestProcessor requestProcessor=new RequestProcessor(socket, servletMap);
            requestProcessor.start();

        }*/

        //6. 线程池改造
        // 上面的代码里面每次进来都是一个RequestProcessor对象，现在将这个放在线程池里面
        // 让线程池来统一管理这个RequestProcessor线程
        // 首先在本方法里面定义一个线程池，来管理即可，不麻烦
        /*while (true) {
            System.out.println("使用线程池进行优化");
            Socket socket = serverSocket.accept();
            RequestProcessor requestProcessor = new RequestProcessor(socket, servletMap);
            //requestProcessor.start(); //以前不用线程池的时候，就是这样的直接statr方法启动
            //这里用线程池的时候，就直接把这个requestProcessor交给线程池去执行即可
            threadPoolExecutor.execute(requestProcessor);
        }*/
        // 线程池的目的，其实就是为了优化，提供性能，避免资源浪费的
        // 像有这种需求的，数据库连接选择数据库连接池等
        // 当并发达到一定数量的时候，性能比不用线程池的效果要好一些
        // 访问： http://127.0.0.1:8080/lagou   servlet睡眠继续开启，来验证
        // 访问： http://127.0.0.1:8080/index.html  这里继续访问
        // 发现请求继续有效，加了线程池，效果未受影响


        //7.作业，实现多工程加载 http://localhost:8080/demo1/lagou
        loadServerServlet();
        while (true) {
            System.out.println("作业，实现多工程加载能力");
            Socket socket = serverSocket.accept();
            MiniReqProcessor requestProcessor = new MiniReqProcessor(socket, mapper);
            threadPoolExecutor.execute(requestProcessor);
        }
    }


    private Map<String, HttpServlet> servletMap = new HashMap<String, HttpServlet>();
    private void loadServlet() {

          InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
            SAXReader saxReader = new SAXReader();

        try {

            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();

            List<Element> selectNodes = rootElement.selectNodes("//servlet");
            for (int i = 0; i < selectNodes.size(); i++) {
                Element element = selectNodes.get(i);
                // <servlet-name>lagou</servlet-name>
                Element servletnameElement = (Element) element.selectSingleNode("servlet-name");
                String servletName = servletnameElement.getStringValue();
                // <servlet-class>server.LagouServlet</servlet-class>
                Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
                String servletClass = servletclassElement.getStringValue();


                // 根据servlet-name的值找到url-pattern
                Element servletMapping = (Element) rootElement.selectSingleNode(
                        "/web-app/servlet-mapping[servlet-name='" + servletName + "']");
                // /lagou 拿路径
                String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
                servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());
            }

        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }

    private Map<String, HttpServlet> wrapper = new HashMap<String, HttpServlet>();
    private void loadServlet(String appBase, String projectName, String webPath) {

        try {
//          InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
            InputStream resourceAsStream = new FileInputStream(webPath);
            SAXReader saxReader = new SAXReader();

            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();

            List<Element> selectNodes = rootElement.selectNodes("//servlet");
            for (int i = 0; i < selectNodes.size(); i++) {
                Element element = selectNodes.get(i);
                // <servlet-name>lagou</servlet-name>
                Element servletnameElement = (Element) element.selectSingleNode("servlet-name");
                String servletName = servletnameElement.getStringValue();
                // <servlet-class>server.LagouServlet</servlet-class>
                Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
                String servletClass = servletclassElement.getStringValue();


                // 根据servlet-name的值找到url-pattern
                Element servletMapping = (Element) rootElement.selectSingleNode(
                        "/web-app/servlet-mapping[servlet-name='" + servletName + "']");
                // /lagou 拿路径
                String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
                String classFullPath=appBase+"/"+projectName+"/"+servletClass.replace(".", "/")+".class";
                wrapper.put(urlPattern, (HttpServlet) new ClazzLoader().findClass(classFullPath, servletClass).newInstance());
            }

        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

    }


    private Mapper mapper = new Mapper();

    private void loadServerServlet() {

        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("server.xml");
        SAXReader saxReader = new SAXReader();

        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();

            List<Element> hostNodes = rootElement.selectNodes("//Server//Service//Engine//Host");
            Host[] hosts = new Host[hostNodes.size()];
            int i = 0;
            for (Element ele_host : hostNodes) {
                String name = ele_host.attributeValue("name");
                String appBase = ele_host.attributeValue("appBase");

                File appBaseFolder = new File(appBase);
                File[] files = appBaseFolder.listFiles();
                Context[] contexts = new Context[files.length];
                int j = 0;
                for (File file : files) {
                    if (file.isDirectory()) {
                        Context context = new Context();
                        String projectName = file.getName();
                        context.setPath(projectName);
                        // 构建Wrappers，一个Wrapper对应一个Servlet
                        File webFile = new File(file, "web.xml");
                        loadServlet(appBase, projectName, webFile.getAbsolutePath());
                        context.setWrappers(wrapper);
                        wrapper = new HashMap<String, HttpServlet>();
                        contexts[j++] = context;
                    }
                }

                Host host = new Host();
                host.setName(name);
                host.setAppBase(appBase);
                host.setContexts(contexts);
                hosts[i++] = host;
            }
            mapper.setHosts(hosts);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        Bootstrap bootstrap = new Bootstrap();
        try {
            bootstrap.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}
